import java.util.PriorityQueue;
import java.util.Random;
import java.util.concurrent.PriorityBlockingQueue;

// 使用这个类来表示一个定时器的任务
class MyTask implements Comparable<MyTask> {
    // 第一个 要执行的任务内容
    private Runnable runnable;
    // 任务啥时候执行 (用时间戳来表示)

    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    // 获取当前代码的时间
    public long getTime() {
        return time;
    }


    // 执行代码
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {

        // 返回小于0 大于 0 或者是 0 这三种情况
        // 如果是 this 比 o 小 返回 < 0
        // 大  返回 >0
        // 相等 返回 =0
        // 当前要实现的效果是 队首元素是时间最小的任务

        // 这俩是谁见谁 一定不要去记,而是试试就知道了,看看符不符合自己的预期
        return (int) (this.time - o.time);
    }
}

// 创建一个定时器
class MyTimer{
    // 要有一个扫描线程 (最难的部分)
    private  Thread t = null;

    // 要有一个 阻塞优先级队列 来保存任务

    // 使用优先级呢是要具体指出 具体要比较哪一个是优先级代表
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); // 此处还有一个小问题 , 这个队列里存的是 "任务" , 此时的任务应该用什么表示呢?
    // 可以用Runnable 表示任务 , 但是 单独用 Runnable 是远远不够的,这只是简单的描述了一个任务内容,还需要描述任务啥时候被执行
    // 所以要再针对这个代码包装一下

    // 如果this有风险的话
    // 可以这样使用,再专门创建一个对象来进行加锁/等待通知
    private Object locker = new Object();

    // 创建线程
    public MyTimer(){
        t = new Thread(() -> {
            while(true){
                // 取出队首元素 , 检查看看队首元素是否到时间了
                try {
                     System.out.println("this2: "+this);
                    synchronized (this  /*这里就需要换成locker*/) { // 加在这里 是确保 take 操作 和 put 操作要一块执行完,防止 在执行完take 操作后 线程突然被cpu调走了
                        // 这样就执行不到wait操作了 , 后面在新增一个任务后 执行 的notify操作就唤醒不了wait操作了
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < myTask.getTime()){
                            // 说明还没有到点, 先不必执行
                            queue.put(myTask); //myTask 重新赛会到队列
                            // 这个队列是优先级队列 , put会触发 优先级 调整 ,调整之后 myTask 又回到队首了
                            // 下次循环取出来的还是这个程序
                            // 而当前的循环是没有阻塞效果的
                            // 所以 在没有到达实行时间的时候 就会一直循环这里  ,, 出现 "忙等的现象"
                            // 所以 要对次代码进行一定的调成 , 要进行阻塞式等待 (sleep , wait)
                            // 此处看似等待时间明确, 实际上并不明确,随时可能有新的任务到来(有线成调用 schedule 添加新任务)
                            // 万一新任务的时间更早呢 , 所以 sleep 的效果不好
                            // 使用wait 等待,每次有新任务来了 , 就用notify,重新检查一下时间,重新计算等待时间
                            // wait 也有一个超时间的版本
                            // 带超时间的版本:
                            // 1. 当新任务来的时候 随时notify唤醒
                            // 2. 如果新任务没有来 则最多等到之前旧任务中最早的时间 就被唤醒

                                //在put 之后进行wait 操作
                                this.wait(myTask.getTime() - curTime); //myTask.getTime() 用执行时间减去当前的时间

                                // locker.wait(myTask.getTime() - curTime);


                            // 此时的等待就不占用cpu , 也不会错过新来的线程
                        }else {
                            // 到时间了 执行任务
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 如果时间没到,就把任务塞回到队列去
                // 如果时间没到,就把任务继续执行
            }
        });
        t.start();
    }

    // 咱们的定时器类现需要又一个 schedule 来表示

    // 指定两个参数
    // 任务内容
    // 任务在多少毫秒之后执行
    public void schedule(Runnable runnable,long after){
        // 注意这里时间上的换算
        MyTask myTask = new MyTask(runnable,System.currentTimeMillis()+after);
        // System.currentTimeMillis() 用当前的时间戳 + after 来计算出我们是什么时间具体执行内容
        queue.put(myTask);
        System.out.println("this: "+this);
        // put 之后就唤醒
        synchronized (this /*locker*/) {
            this.notify();
            // locker.notify();
        }
    }
}


public class ThreadTimerTest {
    public static void main(String[] args) {

        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务一");
            }
        },1000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);

        // 这里直接运行之后呢 会报错的
        // 需要有一个 comparable 接口
        // 也可以自己单独实现一个比较器
    }
}
